/* 

==========================================================

DX490a - Summer 2010

Instructor: Stelios Manousakis

==========================================================

Class 3.2:

Scheduling in SuperCollider: 1

Contents:

• Streams

- Routines

- Tasks

Task in a function

Task in a task

- {}.fork

- loop {}

==========================================================

*/



// ================= STREAMS =================

/* One way to trigger multiple events on the Server through the language in real-time without having to constanly evaluate lines of code is by using timed streams. 

In SC, streams are implemeted as 'lazy' sequences of values. 'Lazy' means that they will only get evaluated when it's their time to give an output, rather than evaluating everything at once, storing it, and then outputing it sequencially, as you would do with, for example, a .do loop.


You get a value from a Stream-type class with the .next or .value message. When it runs out of values it will give you nil. But you can restart most streams with the .reset message. You can also embed one stream inside another stream, allowing one stream to control another using the .embedInStream message


Streams can also be timed using any of the three types of Clocks available in SC; you just pass the clock as an argument to them.

*/




// ====== ROUTINES ======

// One way to write a function as a stream is by using Routine. You separate it's different sequential components with 'yield':


r = Routine.new({ 1.yield; 2.yield; });

r.next.postln;

r.next.postln;

r.next.postln;

r.reset

// a more complex example:

(

r = Routine({

x = 1.0.rand;

2.yield; // 1st

y = 100;

x.yield; // 2nd

"post me!".yield; //3rd

(x * y).yield; //4th

x = 1.0.rand;

x.yield; // 5th

(x * y).yield; // 6th

y = 1000;

(x * y).yield; //7th

});

10.do({ r.value.postln });

)


// You can use it to make sound:

s.boot;

(

SynthDef(\gray, {arg outBus = 0, freq = 400, amp = 1, dur = 1;

var env, src, fdbin, fdbout;

env = EnvGen.kr(Env([0, 1, 0], [0.05, 0.95], \sin), timeScale: dur, levelScale: amp, doneAction: 2);

src = LPF.ar(GrayNoise.ar(amp), freq, env);

Out.ar(outBus, Pan2.ar(src, Rand.new(-0.7, 0.7)));

}).load(s);

)


(

r = Routine({

x = 500.rrand(5000).postln;

"1st".yield; 

Synth(\gray, ([freq:x, amp:0.6]));

"2nd".yield;

x = 500.rrand(5000).postln;

Synth(\gray, ([freq:x, amp:0.6]));

"3rd".yield;

});

)

r.next

r.next

r.next


// You can also use a clock to playback a Routine. In this case, a float or integer before the '.yield' (which can be named .wait as well, btw) is needed to tell the clock how long to wait between each step.

(

r = Routine({

x = 500.rrand(5000).postln;

0.5.yield; 

Synth(\gray, ([freq:x, amp:0.6]));

1.yield;

x = 500.rrand(5000).postln;

Synth(\gray, ([freq:x, amp:0.6]));

0.25.yield;

x = 500.rrand(5000).postln;

Synth(\gray, ([freq:x, amp:0.6]));

});

)

TempoClock.sched(0, r)

r.reset; // must reset before playing again:

TempoClock.sched(0, r)


// more simply though, you can just .play the Routine:

r.reset;

r.play(TempoClock(2)); // the argument for the tempo clock will define the relative playback speed of the routine now

r.reset;

r.play(TempoClock(0.5));



// You can embed embed .do functions inside a Routine, and as this is a 'lazy' event, you can use inf.do to have something that will just keep on playing, without crashing your computer - provided there is a positive wait time!


(

r = Routine({

inf.do({arg i;

x = 500.rrand(1500).postln;

f =  x + (((i % 7) * 1000));

y = 0.4.rrand(0.8).postln;

Synth(\gray, ([freq:f, amp:y]));

0.25.rrand(0.75).wait; // NEVER forget this!!!! 

})

}).play

)

r.stop; // stop the routine

r.reset; // reset

r.play(TempoClock(0.5)); // play slower

r.stop; // stop the routine







// ====== TASKS ======

// A Task is something like a fancier Routine: one that you can pause and resume at will.


(

t = Task({

inf.do({arg i;

x = 500.rrand(1500).postln;

f =  x + (((i % 7) * 1000));

y = 0.4.rrand(0.8).postln;

Synth(\gray, ([freq:f, amp:y]));

0.25.rrand(0.75).wait; // NEVER forget this!!!! 

})

}).play

)


t.pause; 

t.resume;

t.reset;

t.stop;

t.play(TempoClock(0.5)); // play slower

t.stop;




// ------ Task inside a function -- 

// You can create a Task through a function (a slightly modified example by Josh Parmenter):


// first, load this SynthDef:

(

SynthDef(\sine, {arg freq = 400, amp = 1, dur = 1;

var env, src;

env = EnvGen.kr(Env([0, 1, 0], [0.5, 0.5], \sin), timeScale: dur, levelScale: amp, doneAction: 2);

src = SinOsc.ar(freq, 0, env);

Out.ar(0, Pan2.ar(src, Rand.new(-0.7, 0.7)));

}).load(s);

)


// now, a function that incorporates a Task:

(

~tFunc = {arg basefreq, numnotes;

var task;

task = Task({

var freqs, durs, amps;

// calculate an array of frequencies for the notes

freqs = Array.fill(numnotes, {arg i;

basefreq * ((i + 1) - 0.5).rrand((i + 1) + 0.5)});

freqs.postln;

// calculate an array of durations for the notes

durs = Array.fill(numnotes, {arg i; (numnotes-(i-1.0)).rand});

durs.postln;

// calculate an array of amplitudes for the notes

amps = Array.fill(numnotes, {arg i; (i + 1).reciprocal});

amps.postln;

// now generate the notes

numnotes.do({

arg i;

s.sendBundle(0.1, [\s_new, \sine, s.nextNodeID, 0, 1, \freq, freqs[i], \dur, durs[i], \amp, amps[i] * amps.sum.reciprocal]);

0.1.rrand(0.5).wait; // and wait a bit between each new note

})

}); // this is where the Task ends

task.play; // you have to tell the function to play it!

};

);


a = ~tFunc.value(440, 10); // this returns the Task... so it can still be paused and resumed!

b = ~tFunc.value(220, 30);

a.pause;

a.resume;

b.pause;

b.resume;




// ------ A Task of Tasks --

///// You can also create a task of tasks:

// Use almost the same function as above, except it doesn't play the task


(

~tFunc = {arg basefreq, numnotes;

var task;

task = Task({

var freqs, durs, amps;

// calculate an array of frequencies for the notes

freqs = Array.fill(numnotes, {arg i;

basefreq * ((i + 1) - 0.5).rrand((i + 1) + 0.5)});

freqs.postln;

// calculate an array of durations for the notes

durs = Array.fill(numnotes, {arg i; (numnotes-(i-1.0)).rand});

durs.postln;

// calculate an array of amplitudes for the notes

amps = Array.fill(numnotes, {arg i; (i + 1).reciprocal});

amps.postln;

// now generate the notes

numnotes.do({

arg i;

s.sendBundle(0.1, [\s_new, \sine, s.nextNodeID, 0, 1, \freq, freqs[i], \dur, durs[i], \amp, amps[i] * amps.sum.reciprocal]);

0.1.rrand(0.5).wait; // and wait a bit between each new note

})

}); // this is where the Task ends

// don't tell the function to play the task

};


// ~tFuncArray is an array of Tasks

~tFuncArray = Array.fill(10, {arg i; ~tFunc.value(220.rrand(440) * (i + 1), 10.rrand(20))});

~tFuncArray.postln;

// iterate through the array of Tasks, play them, and wait in between, with a Task of Tasks

~tFuncArrayTask = Task({

~tFuncArray.do({arg aTask, i;

aTask.postln;

aTask.play;

1.rrand(5).wait;

});

});

)


~tFuncArray[3].play; // play a specific task

~tFuncArray[3].stop; // play a specific task

~tFuncArray.choose.play; // play a task at random

~tFuncArrayTask.play; // play the task of tasks

~tFuncArrayTask.pause;

~tFuncArrayTask.resume;

~tFuncArrayTask.reset;

~tFuncArrayTask.play;


// pause the Task of Tasks AND each individual Task (if it is playing)

(

~tFuncArrayTask.pause;

// iterate over the array of Tasks 

~tFuncArray.do({arg aTask, i; 

// aTask is the individual Task in each slot of the Array... check if it is playing

aTask.pause;

})

)

~tFuncArrayTask.resume;

~tFuncArrayTask.stop;





// ====== {}.FORK======

// You can also use the .fork method and create a stream by looping a function. This is is essentially a shortcut to wrap your function in a Routine and play it as a stream. You can pass the Clock you want to use as an argument to  fork:


(

Speech.init(16);

{16.do{|i| 

i.postln;

Speech.setSpeechRate(i, 20.rrand(300));

Speech.channels[i].voice_(i).speak("d'oh!");

0.2.rrand(0.5).wait;

}}.fork(TempoClock)

)




// ====== LOOP {}======

// Instead of inf.do, you can also use a loop around your stream function. 

// (Do make sure that you never use either inf.do or a loop in any construct that doesn't incorporate .wait or .yield!!!)


(

t = Task({

loop ({

x = 500.rrand(5000).postln;

y = 0.4.rrand(0.8).postln;

Synth(\gray, ([freq:x, amp:y]));

0.25.rrand(0.75).wait; // NEVER forget this!!!! 

})

})

)

t.play;

t.stop;



/* Have a look here:

[Stream]

[Streams-Patterns-Events1] 

[ServerTiming]

*/